#include "png8cropfx.h"
#include "dib4source.h"

namespace png8crop
{

	dib4source::dib4source(FILE* dibsrc)
		:pixrows_alloc(NULL),
		palbuf_alloc(NULL),
		tranbuf_alloc(NULL),
		pixbuf_alloc(NULL)
	{
		try
		{
			this->load_dib(dibsrc);
		}
		catch(ex_generic &ex)
		{
			this->~dib4source();

			ex.print_msg();
			throw ex;
		}
	}

	void dib4source::load_dib(FILE* dibsrc)
	{
		try
		{
			::BITMAPFILEHEADER file_header;
			::BITMAPINFOHEADER info_head;

			if(fread(&file_header, sizeof(byte), sizeof(file_header), dibsrc) != sizeof(BITMAPFILEHEADER))
			{
				throw ex_dataformat(_T("Cannot read bmp header from file."));
			}

			if(file_header.bfType != 0x4d42) //'BM'
			{
				throw ex_dataformat(_T("Input file is not a bitmap."));
			}

			if(fread(&info_head, sizeof(byte), sizeof(info_head), dibsrc) != sizeof(BITMAPINFOHEADER))
			{
				throw ex_dataformat(_T("Cannot read bmp info from file."));
			}

			if(info_head.biBitCount != 4 || info_head.biCompression != BI_RGB)
			{
				throw ex_dataformat(_T("Not a correct 4-bit bitmap."));
			}

			// init members

			this->plane_height = abs(info_head.biHeight);

			this->size.w = info_head.biWidth;
			this->size.h = this->plane_height * PLANES_PER_IMAGE;
			
			// plane params
			//aligned to 256bit for extending 128bit-aligned dib4
			this->scan_width = (info_head.biWidth + ALIGN32_MASK) & (~ALIGN32_MASK);
			int planearea = (this->scan_width * this->plane_height + ALIGN16_MASK) & (~ALIGN16_MASK);

			this->pixbuf_alloc = new png_byte[planearea * PLANES_PER_IMAGE];
			if( this->pixbuf_alloc == NULL )
			{
				throw ex_internal(_T("Memery allocating failed."), _T("pixbuf_alloc"));
			}

			this->palette = this->palbuf_alloc = new png_color[PNG_PALETTE_SIZE];
			if( this->palbuf_alloc == NULL )
			{
				throw ex_internal(_T("Memery allocating failed."), _T("palbuf_alloc"));
			}

			this->trans_alpha = this->tranbuf_alloc = new png_byte[PNG_PALETTE_SIZE];
			if( this->tranbuf_alloc == NULL )
			{
				throw ex_internal(_T("Memery allocating failed."), _T("tranbuf_alloc"));
			}

			this->rows = this->pixrows_alloc = new png_bytep[this->size.h];
			if( this->pixrows_alloc == NULL )
			{
				throw ex_internal(_T("Memery allocating failed."), _T("pixrows_alloc"));
			}

			if( fseek(dibsrc, file_header.bfOffBits, SEEK_SET) )
			{
				throw ex_dataformat(_T("Locating bitmap data failed."));
			}

			this->bit_depth = 8;
			this->color_type = PNG_COLOR_TYPE_PALETTE;
			this->num_palette = this->num_trans = PNG_PALETTE_SIZE;

			//initialize palette.
			memset(this->palette, 0, PNG_PALETTE_SIZE * sizeof(png_color));
			memset(this->trans_alpha, 0, PNG_PALETTE_SIZE);
			
			const png_color scheme0[COLORREF_PER_PALSET] =	{
					{0, 0, 0},	//b
					{0, 0, 0}, //p
					{64, 64, 64}, //e1
					{32, 32, 32}, //e2
				};

			const png_byte scheme0_alpha[COLORREF_PER_PALSET] = {0,  255, 192, 192 };

			for(int i=0; i < PALSET_PER_SCHEME; ++i)
			{
				for(int j=0; j < COLORREF_PER_PALSET; ++j)
				{
					int index = i * COLORREF_PER_PALSET + j;
					this->palette[index] = scheme0[j];
					this->trans_alpha[index] = scheme0_alpha[j];
				}
			}

			//initialize planes
			this->pixels = (png_byte*) (((long) this->pixbuf_alloc + ALIGN16_MASK) & (~ALIGN16_MASK));	//align to 16bytes for sse.

			int pstep;
			int pstart = (info_head.biHeight > 0) ? this->plane_height + (pstep = - 1) : (pstep = 1) & 0;
			int pend = pstart - info_head.biHeight;

			//plane1
			for(int hi = pstart; hi != pend ; hi += pstep)
			{
				this->rows[hi] = this->pixels + (this->scan_width * hi);

				unsigned int num_bytes = ((this->size.w + ALIGN8_MASK) & (~ALIGN8_MASK)) >> 1;
				if( fread(this->rows[hi], sizeof(png_byte), num_bytes, dibsrc) != num_bytes )
				{
					throw ex_dataformat(_T("Corrupted bitmap data."));
				}

				//extend 4bit to 8bit
				/*for(int wi = this->size.w - 1; wi >= 0; --wi)
				{
					png_byte t = this->rows[hi][wi >> 1];
					this->rows[hi][wi] = (wi & 1) ?  (t & 0x0f) : (t & 0xf0) >> 4;
				}*/
				
				const __m128i mmlo4_mask = _mm_set1_epi8(0x0f);
				for(__m128i *ep = (__m128i *) this->rows[hi], *dp = (__m128i *)(this->rows[hi] + this->scan_width), *sp = (__m128i *)(((long)ep + (long)dp) >> 1) - 1;
					sp >= ep; sp--)
				{
					__m128i mmhi4, mmlo4;
					mmlo4 = _mm_and_si128(*sp, mmlo4_mask);
					mmhi4 = _mm_and_si128(_mm_srli_epi16(*sp, 4), mmlo4_mask);
					*(--dp) = _mm_unpackhi_epi8(mmhi4, mmlo4); 
					*(--dp) = _mm_unpacklo_epi8(mmhi4, mmlo4);
				}
			}

			//2nd plane: actived.
			//for(png_bytep sp = this->pixels, dp = sp + planearea; sp < this->pixels + planearea; *(dp++) = *(sp++) + COLORREF_PER_PALSET);
			const __m128i mmadd_offset = _mm_set1_epi8(COLORREF_PER_PALSET);
			for(__m128i *sp = (__m128i *)this->pixels, *dp = (__m128i *)(this->pixels + planearea),	*ep = dp;
				sp < ep; (*dp++) =_mm_add_epi8(*sp++, mmadd_offset)
				);

			//3rd plane: normal.
			memset(this->pixels + (planearea << 1), 0, planearea);
			
			pstart = this->plane_height;
			pend = (pstart << 1);
			for(int hi = pstart; hi != pend; ++hi)
			{
				this->rows[hi] = this->rows[hi - this->plane_height] + planearea;
				this->rows[hi + this->plane_height] = this->rows[hi] + planearea;
			}
		}
		catch(...)
		{
			this->release();
			throw;
		}
	}
	
	void dib4source::release()
	{
		if(palbuf_alloc != NULL) { delete[] palbuf_alloc; palbuf_alloc=NULL; }
		if(tranbuf_alloc != NULL) { delete[] tranbuf_alloc; pixbuf_alloc=NULL;}
		if(pixbuf_alloc != NULL) { delete[] pixbuf_alloc; pixbuf_alloc=NULL;}
		if(pixrows_alloc != NULL) { delete[] pixrows_alloc; pixrows_alloc=NULL;}
	}

	dib4source::~dib4source()
	{
		this->release();
	}

	void dib4source::load_palette(FILE* clbfile, FILE* csmfile)
	{
		__declspec(align(16)) yuv601_color clb_palette[BASIC_PALETTE_SIZE];

		if(fread(clb_palette, sizeof(yuv601_color), BASIC_PALETTE_SIZE, clbfile) != BASIC_PALETTE_SIZE)
		{
			throw ex_dataformat(_T("Read clb file failed."));
		}
		
		this->yuv601torgb((__m128i*) &clb_palette[0]);
		this->yuv601torgb((__m128i*) &clb_palette[4]);
		this->yuv601torgb((__m128i*) &clb_palette[8]);
		this->yuv601torgb((__m128i*) &clb_palette[12]);

		for(int i = 0; i < BASIC_PALETTE_SIZE; ++i)
		{
			this->schemes.palette.color[i] = clb_palette[i].rgb;
		}
		
		mnu_parser parser;
		const int constvals[] = { 1, 2, 3, 4, 5, 6, 7, 8, 
								9, 10, 11, 12, 13, 14, 15, 16};

		parser.register_const(_T("sel_color_3"), &constvals[0], vt_dword);
		parser.register_const(_T("sel_opacity_3"), &constvals[1], vt_dword);
		parser.register_const(_T("sel_color_2"), &constvals[2], vt_dword);
		parser.register_const(_T("sel_opacity_2"), &constvals[3], vt_dword);
		parser.register_const(_T("sel_color_1"), &constvals[4], vt_dword);
		parser.register_const(_T("sel_opacity_1"), &constvals[5], vt_dword);
		parser.register_const(_T("sel_color_0"), &constvals[6], vt_dword);
		parser.register_const(_T("sel_opacity_0"), &constvals[7], vt_dword);
		parser.register_const(_T("act_color_3"), &constvals[8], vt_dword);
		parser.register_const(_T("act_opacity_3"), &constvals[9], vt_dword);
		parser.register_const(_T("act_color_2"), &constvals[10], vt_dword);
		parser.register_const(_T("act_opacity_2"), &constvals[11], vt_dword);
		parser.register_const(_T("act_color_1"), &constvals[12], vt_dword);
		parser.register_const(_T("act_opacity_1"), &constvals[13], vt_dword);
		parser.register_const(_T("act_color_0"), &constvals[14], vt_dword);
		parser.register_const(_T("act_opacity_0"), &constvals[15], vt_dword);

		diam_info diams[2] = { {sizeof(scheme), 3}, {sizeof(png_byte), sizeof(scheme)} };
		diam_list diamlist = { 2, diams };
		parser.register_variable(_T("::menued::color"), &this->schemes.schemes[1], vt_byte, &diamlist, NULL, NULL);

		
		_TCHAR linebuf[MAX_PATH_EXT];
		while(!feof(csmfile))
		{
			_fgetts(linebuf, MAX_PATH_EXT, csmfile);
			try
			{
				parser.parse_line(linebuf);
			}
			catch(ex_generic &ex)
			{
				ex.print_msg();
			}
		}

		for(int i = 1; i < SCHEME_NUM_MAX; ++i)
		{
			for(int j = 0; j < PALSET_PER_SCHEME; ++j)
			{
				for(int k = 0; k < COLORREF_PER_PALSET; ++k)
				{
					palette_refer refer = this->schemes.schemes[i].all[j].all[k].to_referer16();
					int index = (i * PALSET_PER_SCHEME * COLORREF_PER_PALSET) + (j * COLORREF_PER_PALSET) + k;
					this->palette[index] = this->schemes.palette.color[refer.color_id];
					this->trans_alpha[index] = refer.opacity;
				}
			}
		}
	}

	inline void dib4source::yuv601torgb(__m128i *yuv)
	{
		const double kr = 0.299, kb = 0.114;	//http://www.itu.int/rec/R-REC-BT.601/en

		const double krn = 1.0 - kr, kbn = 1.0 - kb, kgn = krn * kbn / (krn + kbn - 1.0);
		const int qind = 22;

		const double qs = (1 << 22);
		const int ys = (int)( 255.0 / 219.0 * qs);
		const int cs = (int)(255.0 / 112.0 * qs);
		const int crm1 = (int)(krn * cs);
		const int crm2 = (int)((kgn - krn) * cs);
		const int cbm1 = (int)((kgn - kbn) * cs);
		const int cbm2 = (int)(kbn * cs);

		__m128i y, cr, cb, x, mask = _mm_set1_epi32(0x000000ff);

		y = _mm_and_si128(_mm_srli_epi32(*yuv, 8), mask);
		cr = _mm_and_si128(_mm_srli_epi32(*yuv, 16), mask);
		cb = _mm_and_si128(_mm_srli_epi32(*yuv, 24), mask);

		y = _mm_sub_epi32(y, _mm_set1_epi32(16));
		y = _mm_mullo_epi32(y, _mm_set1_epi32(ys));
		y = _mm_add_epi32(y, _mm_set1_epi32(1 << (qind - 1)));	//y = (1.164 * (y - 16) + 0.5) * Q

		cb = _mm_sub_epi32(cb, _mm_set1_epi32(128));	//cb = (cb - 128)

		x = _mm_sub_epi32(cr, _mm_set1_epi32(128));	//x = (cr - 128)

		cr = _mm_sub_epi32(y, _mm_mullo_epi32(x, _mm_set1_epi32(crm2)));	//cr = y - x * 0.813 * Q

		mask = _mm_mullo_epi32(cb, _mm_set1_epi32(cbm1));	//re-using mask as temp, mask = cb * 0.392 * Q
		cr = _mm_srai_epi32(_mm_sub_epi32(cr, mask), qind);	//g = (cr - mask) / Q;

		mask = _mm_mullo_epi32(cb, _mm_set1_epi32(cbm2));	//mask = cb * 2.017 * Q
		cb = _mm_srai_epi32(_mm_add_epi32(y, mask), qind);	//b = (y - mask) / Q;

		mask = _mm_mullo_epi32(x, _mm_set1_epi32(crm1));	//mask = x * 1.596 * Q
		y  = _mm_srai_epi32(_mm_add_epi32(y, mask), qind);	//r =  (y + mask) / Q);

		x = _mm_packus_epi32(_mm_set1_epi32(0), y);	//0a0a0a0a0r0r0r0r
		y = _mm_packus_epi32(cr, cb);	//0g0g0g0g0b0b0b0b

		cr = _mm_unpacklo_epi16(x, y);	//0a0g0a0g0a0g0a0g
		cb = _mm_unpackhi_epi16(x, y);	//0r0b0r0b0r0b0r0b

		y = _mm_unpacklo_epi16(cr, cb);
		x = _mm_unpackhi_epi16(cr, cb); //0a0r0g0b0a0r0g0b...

		*yuv = _mm_packus_epi16(y, x);
	}


	void dib4source::update_crop_scheme(schemed_crop *crop_info, int num_scheme)
	{
		for(int i = 0; i < num_scheme; ++i)
		{
			const __m128i pal_delta = _mm_set1_epi8( crop_info[i].scheme_id << COLORREF_PER_SCHEME_IND );
			const __m128i pal_mask = _mm_set1_epi8(~( -1 << COLORREF_PER_SCHEME_IND));

			const crop &crp = crop_info[i].crop;
			
			const png_uint_32 cropb = crop_info[i].crop.y + crop_info[i].crop.h;

			const __m128i maskff = _mm_set1_epi8(-1);
			const __m128i shr = _mm_set_epi8(0x7f, 0x7e, 0x7d, 0x7c, 0x7b, 0x7a, 0x79, 0x78, 0x77, 0x76, 0x75, 0x74, 0x73, 0x72, 0x71, 0x70);
			const __m128i shl = _mm_set_epi8(0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, 0x79, 0x7a, 0x7b, 0x7c, 0x7d, 0x7e, 0x7f);

			__m128i msk_head, msk_tail;
			int ind_head, ind_tail;
			const int ofs_head = (ind_head = crp.x) & ALIGN16_MASK;
			const int ofs_tail = ALIGN16_MASK - ((ind_tail = (crp.x + crp.w - 1)) & ALIGN16_MASK);
			ind_head &= (~ALIGN16_MASK);
			ind_tail &= (~ALIGN16_MASK);

			msk_head = _mm_shuffle_epi8(maskff, _mm_add_epi8(shl, _mm_set1_epi8((char) ofs_head)));		//requirs SSE3
			msk_tail = _mm_shuffle_epi8(maskff, _mm_add_epi8(shr, _mm_set1_epi8((char) ofs_tail)));

			for(png_uint_32 hi = crp.y; hi < cropb; ++hi)
			{
				__m128i *ppix = (__m128i*)(&this->rows[hi][ind_head]);
				size_t delta = (__m128i*)(&this->rows[hi + this->plane_height][ind_head]) - ppix;

				*ppix = _mm_add_epi8( _mm_and_si128(*ppix, pal_mask), _mm_and_si128(pal_delta, msk_head));
				*(ppix + delta) = _mm_add_epi8( _mm_and_si128(*(ppix + delta), pal_mask), _mm_and_si128(pal_delta, msk_head));

				for(ppix++; ppix != (__m128i *) &this->rows[hi][ind_tail]; ppix++)
				{
					*ppix = _mm_add_epi8(_mm_and_si128(*ppix, pal_mask), pal_delta);
					*(ppix + delta) = _mm_add_epi8(_mm_and_si128(*(ppix + delta), pal_mask), pal_delta);
				}

				*ppix = _mm_add_epi8( _mm_and_si128(*ppix, pal_mask), _mm_and_si128(pal_delta, msk_tail));
				*(ppix + delta) = _mm_add_epi8( _mm_and_si128(*(ppix + delta), pal_mask), _mm_and_si128(pal_delta, msk_tail));
			}
		}
	}
}